package br.ufrj.dcc.dm.action;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;

import br.ufrj.dcc.dm.model.Entry;

public class EntropyCalc 
{

	private static final String POSITIVE_YES = "yes";
	private static final String POSITIVE_TRUE = "true";
	private static final String NEGATIVE_NO = "no";
	private static final String NEGATIVE_FALSE = "false";
	
	private ArrayList<Entry> entries;
	
	public EntropyCalc(ArrayList<Entry> entries)
	{
		this.entries = entries;
	}
	
	public Double calculateRootEntropy()
	{
		
		Double entropy = new Double(0);
		
		if(entries.isEmpty())
		{
			return Double.NaN;
		}
		
		int total = entries.size();
		
		int positive = calculatePositiveFrequency(entries);
		int negative = calculateNegativeFrequency(entries);
		
		double positiveProbability = positive/ (double) total;
		double negativeProbability = negative/ (double) total;
	
		entropy = -(positiveProbability * log2(positiveProbability) + negativeProbability * log2(negativeProbability));
		
		if(entropy == 0)
		{
			if(positive > negative)
			{
				return Double.POSITIVE_INFINITY;
			}
			else
			{
				return Double.NEGATIVE_INFINITY;
			}
		}
		return entropy;
	}
	
	public Double calculateRootEntropy(ArrayList<Entry> entries)
	{
		
		Double entropy = new Double(0);
		
		if(entries.isEmpty())
		{
			System.out.println("EMPTYYYYYYYYYYYYYYYYYYY");
			return Double.NaN;
		}
		
		int total = entries.size();
		
		int positive = calculatePositiveFrequency(entries);
		int negative = calculateNegativeFrequency(entries);
		
		double positiveProbability = positive/ (double) total;
		double negativeProbability = negative/ (double) total;
	
		if(positiveProbability != 0)
		{
			entropy += -(positiveProbability * log2(positiveProbability));
		}
		
		if(negativeProbability != 0)
		{
			entropy += -(negativeProbability * log2(negativeProbability));
		}
		
		return entropy;
	}
	
	public Double calculateAttributeEntropy(int attributeIndex)
	{
		Double entropy = new Double(0);
		
		Set<String> possibleAttributes = new HashSet<String>();
		
		if(entries.isEmpty())
		{
			return Double.NaN;
		}
		
		for(Entry entry : entries)
		{
			possibleAttributes.add(entry.get(attributeIndex).getValue());
		}
		
		Double[] probabilities = new Double[possibleAttributes.size()];
		
		for(int i = 0; i < possibleAttributes.size(); i ++)
		{
			probabilities[i] = calculateProbability(possibleAttributes.toArray()[i].toString(), attributeIndex);
		}
		
		for(int i = 0; i < probabilities.length; i ++)
		{
			entropy += probabilities[i]*calculateSpecificAttributeEntropy(possibleAttributes.toArray()[i].toString(), attributeIndex, probabilities[i]);
		}

		return entropy;
	}
	
	protected Double calculateSpecificAttributeEntropy(String attributeValue, int attributeIndex, Double probability)
	{
		ArrayList<Entry> specificEntries = new ArrayList<Entry>();
		
		for(Entry e : entries)
		{
			if(e.get(attributeIndex).getValue().equals(attributeValue))
			{
				specificEntries.add(e);
			}
		}
		
		Double attributeEntropy = calculateRootEntropy(specificEntries);
		
		return attributeEntropy;
	}
	
	protected Double calculateProbability(String attributeValue, int attributeIndex)
	{
		int total = entries.size();
		int frequency = calculateAttributeFrequency(attributeValue , attributeIndex);
		
		Double probability = frequency / (double) total;
		
		return probability;
	}
	
	protected int calculateAttributeFrequency(String attributeValue, int attributeIndex)
	{
		int frequency = 0;
		
		for(Entry entry : entries)
		{
			if(entry.get(attributeIndex).getValue().equals(attributeValue))
			{
				frequency ++;
			}
		}
		
		return frequency;
	}
	
	private int calculatePositiveFrequency(ArrayList<Entry> entries)
	{
		int frequency = 0;
		
		for(Entry e : entries)
		{
			if(e.getClassAttribute().getValue().equalsIgnoreCase(POSITIVE_YES) || e.getClassAttribute().getValue().equalsIgnoreCase(POSITIVE_TRUE))
			{
				frequency ++;
			}
		}
		
		return frequency;
	}
	
	private int calculateNegativeFrequency(ArrayList<Entry> entries)
	{
		int frequency = 0;
		
		for(Entry e : entries)
		{
			if(e.getClassAttribute().getValue().equalsIgnoreCase(NEGATIVE_NO) || e.getClassAttribute().getValue().equalsIgnoreCase(NEGATIVE_FALSE))
			{
				frequency ++;
			}
		}
		
		return frequency;
	}
	
	protected double log2(double a)
	{	
		if(a == 0)
		{
			return 0;
		}
		 
		return Math.log(a)/Math.log(2);
	}
}
